package Model;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/** 
 *  L'élément 'polygon' definit une forme fermee consistant en un jeu de segments de droite relies.
 *  
 *  @author Kilian Cousein et Benjamin Tardieu
 */
public class Polygone extends Motif
{
//======================================================================//
//============================ Variables ===============================//
//======================================================================//
	
	private int pointX[]; // coordonnées x
	private int pointY[]; // coordonnées y
	private int rayon; // rayon
	private int decalage; // décalage du polygone 
	private int nbPoint = 6; // nombre de point du polygone
	
	private int minX; // valeur minimal des coordonnées x
	private int minY; // valeur minimal des coordonnées y
	private int maxX; // valeur maximal des coordonnées x
	private int maxY; // valeur maximal des coordonnées y


//======================================================================//
//========================== Constructeurs =============================//
//======================================================================//
		
	/**
	 * Crée une nouvelle instance de <i>Polygone</i> par défaut.
	 */
	public Polygone()
	{
	}
	
	
	/**
	 * Crée une nouvelle instance de <i>Polygone</i> qui sera un clone de <i>motif</i>.
	 * 
	 * @param motif Motif
	 */
	public Polygone(Motif motif)
	{	
		super(motif);
		Polygone m = (Polygone) motif;
		
		this.rayon = m.rayon;
		this.decalage = m.decalage;
		
		this.nbPoint = m.nbPoint;
		
		this.pointX = new int[this.nbPoint];
		this.pointY = new int[this.nbPoint];
		
		for(int i = 0; i < this.nbPoint; i++)
		{
			this.pointX[i] = m.pointX[i];
			this.pointY[i] = m.pointY[i];
		}
		
		this.minX = m.minX;
		this.minY = m.minY;
		
		this.maxX = m.maxX;
		this.maxY = m.maxY;
	}
	
	
//======================================================================//
//==================== Écriture méthodes abstraites ====================//
//======================================================================//
	
	@Override
	public boolean contient(Point point)
	{
		return (this.x - this.rayon <= point.x) && (this.y - this.rayon <= point.y) && (point.x <= (this.x - this.rayon) + (this.rayon * 2)) && (point.y <= (this.y - this.rayon) + (this.rayon * 2));
	}

	
	@Override
	public void deplacer(int x, int y)
	{
		this.x += x;
		this.y += y;
		
		this.minX += x;
		this.minY += y;
		this.maxX += x;
		this.maxY += y;
		
		for(int i = 0; i < this.nbPoint; i++)
		{
			this.pointX[i] += x;
			this.pointY[i] += y;
		}
	}
	

	@Override
	public void dessinerMotif(Graphics2D g)
	{
		g.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, this.opacite));	
		g.setColor(this.couleur);
		g.fillPolygon(this.pointX, this.pointY, this.nbPoint);	
		
		if(this.largeurBordure > 0)
		{
			g.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, this.opaciteBordure));	
			g.setColor(this.couleurBordure);
			g.setStroke(new BasicStroke(this.largeurBordure));
			g.drawPolygon(this.pointX, this.pointY, this.nbPoint);
		}
		
		g.setComposite(java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, 1.0f));	
	}

	
	@Override
	public void dessinerPoignees(Graphics2D g)
	{
		g.setStroke(new BasicStroke(1));
		int largeur = this.maxX - this.minX;
		int hauteur = this.maxY - this.minY;
		
		g.setColor(Color.white);
		g.fillRect(this.minX, this.minY, HANDLE_SIZE, HANDLE_SIZE); // left top
		g.fillRect(this.minX + largeur - HANDLE_SIZE, this.minY, HANDLE_SIZE, HANDLE_SIZE); // right top
		g.fillRect(this.minX, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE); // left bottom
		g.fillRect(this.minX + largeur - HANDLE_SIZE, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE);// right bottom

		g.fillRect(this.minX, this.minY + (hauteur - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE); // gauche
		g.fillRect(this.minX + (largeur - HANDLE_SIZE) / 2, this.minY, HANDLE_SIZE, HANDLE_SIZE); // haut
		g.fillRect(this.minX + largeur - HANDLE_SIZE, this.minY + (hauteur - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE); // droite
		g.fillRect(this.minX + (largeur - HANDLE_SIZE) / 2, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE); // bas
		
		g.setColor(Color.gray);
		g.drawRect(this.minX , this.minY, HANDLE_SIZE, HANDLE_SIZE); // left top
		g.drawRect(this.minX + largeur - HANDLE_SIZE, this.minY, HANDLE_SIZE, HANDLE_SIZE); // right top
		g.drawRect(this.minX, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE); // left bottom
		g.drawRect(this.minX + largeur - HANDLE_SIZE, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE);// right bottom

		g.drawRect(this.minX, this.minY + (hauteur - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE); // gauche
		g.drawRect(this.minX + (largeur - HANDLE_SIZE) / 2, this.minY, HANDLE_SIZE, HANDLE_SIZE); // haut
		g.drawRect(this.minX + largeur - HANDLE_SIZE, this.minY + (hauteur - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE); // droite
		g.drawRect(this.minX + (largeur - HANDLE_SIZE) / 2, this.minY + hauteur - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE); // bas
		
		float[] dash3 = { 4f, 0f, 2f };
		g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f));
		g.drawRect(this.minX, this.minY, largeur, hauteur);		
	}
	

	@Override
	public Element enregistrer(Document doc)
	{
		Element polygone = doc.createElement("polygon");
		String points = "";
		
		for(int i = 0; i < this.nbPoint; i++)
		{
			points += this.pointX[i]+","+this.pointY[i]+" ";
		}
		
		polygone.setAttribute("points", ""+points);
		
		if(this.couleur != null)
			polygone.setAttribute("fill", "rgb("+this.couleur.getRed()+","+this.couleur.getGreen()+","+this.couleur.getBlue()+")");
		
		if(this.largeurBordure > 0)
		{
			polygone.setAttribute("stroke-width", ""+this.largeurBordure);
			polygone.setAttribute("stroke", "rgb("+this.couleurBordure.getRed()+","+this.couleurBordure.getGreen()+","+this.couleurBordure.getBlue()+")");
		}
		
		if(this.opacite < 1.0f)
		{
			polygone.setAttribute("opacity", ""+this.opacite);
		}
		
		if(this.opaciteBordure < 1.0f)
		{
			polygone.setAttribute("opacity-stoke", ""+this.opaciteBordure);
		}
		
		return polygone;
	}
	
	@Override
	public void ouvrir(Node node)
	{
		Node attribut;
		
		for(int j = 0; j < node.getAttributes().getLength(); j++)
		{			
			attribut = node.getAttributes().item(j);
			
			if(attribut.getNodeName() == "points")
			{
				String points[];		
				String coordonnes[];
				String point = attribut.getNodeValue();
				
				this.nbPoint = 0;
				points = point.split(" ");
				
				for(int i = 0; i < points.length; i++)
				{
					points[i].replaceAll(" ", "");
					
					if(points[i].matches("[0-9]{1,3},[0-9]{1,3}"))
						this.nbPoint++;
				}
				
				this.pointX = new int[this.nbPoint];
				this.pointY = new int[this.nbPoint]; 
				
				coordonnes = points[0].split(",");
				this.pointX[0] = Integer.valueOf(coordonnes[0]);
				this.pointY[0] = Integer.valueOf(coordonnes[1]);
				
				this.minX = this.pointX[0];
				this.minY = this.pointY[0];
				this.maxX = this.pointX[0];
				this.maxY = this.pointY[0];
				
				for(int i = 1; i < points.length; i++)
				{
					coordonnes = points[i].split(",");
					this.pointX[i] = Integer.valueOf(coordonnes[0]);
					this.pointY[i] = Integer.valueOf(coordonnes[1]);
					
					if(this.minX > this.pointX[i])
						this.minX = this.pointX[i];
					
					if(this.minY > this.pointY[i])
						this.minY = this.pointY[i];
					
					if(this.maxX < this.pointX[i])
						this.maxX = this.pointX[i];
					
					if(this.maxY < this.pointY[i])
						this.maxY = this.pointY[i];
				}
				
				this.rayon = (int) (Math.sqrt(((this.pointX[this.nbPoint/2] - this.pointX[0]) * (this.pointX[this.nbPoint/2] - this.pointX[0])) + ((this.pointY[this.nbPoint/2] - this.pointY[0]) * (this.pointY[this.nbPoint/2] - this.pointY[0]))) / 2);
				this.x = (this.pointX[0] + this.pointX[this.nbPoint/2]) / 2;
				this.y = (this.pointY[0] + this.pointY[this.nbPoint/2]) / 2;
			}
			else if(attribut.getNodeName() == "fill")
				this.setCouleur(this.convertitStringCouleur(attribut.getNodeValue()));
			else if(attribut.getNodeName() == "stroke-width")
				this.setLargeurBordure(Integer.valueOf(attribut.getNodeValue()));
			else if(attribut.getNodeName() == "stroke")
				this.setCouleurBordure(this.convertitStringCouleur(attribut.getNodeValue()));
			else if(attribut.getNodeName() == "opacity")
			{
				this.setOpaciteCouleur(Float.valueOf(attribut.getNodeValue()));
				this.setOpaciteBordure(Float.valueOf(attribut.getNodeValue()));
			}
			else if(attribut.getNodeName() == "stroke-opacity")
				this.setOpaciteBordure(Float.valueOf(attribut.getNodeValue()));
		}
	}
	

	@Override
	public void faireCorrespondre(int ox, int oy, int x, int y)
	{
		this.x = ox;
		this.y = oy;		
		
		this.rayon = (int) Math.sqrt(((x - this.x) * (x - this.x)) + ((y - this.y) * (y - this.y)));
		this.decalage = y - this.y;
		
		this.calculerPolygone();
	}
	
	
	@Override
	public void findHandles(Point point)
	{
		this.poigneeSelectionne = getHandles(point.x, point.y);
	}

	
	@Override
	public void redimensionner(int x1, int x2, int y1, int y2)
	{
		this.rayon = (int) Math.sqrt(((x1 - this.x) * (x1 - this.x)) + ((y1 - this.y) * (y1 - this.y)));
		
		this.pointX[0] = (int) (this.rayon * Math.cos (((0 * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.x;
		this.pointY[0] = (int) (this.rayon * Math.sin (((0 * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.y;
		
		this.minX = this.pointX[0];
		this.minY = this.pointY[0];
		this.maxX = this.pointX[0];
		this.maxY = this.pointY[0];
		
		for(int i = 1; i < this.nbPoint; i++)
		{
			this.pointX[i] = (int) (this.rayon * Math.cos (((i * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.x;
			this.pointY[i] = (int) (this.rayon * Math.sin (((i * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.y;
			
			if(this.minX > this.pointX[i])
				this.minX = this.pointX[i];
			
			if(this.minY > this.pointY[i])
				this.minY = this.pointY[i];
			
			if(this.maxX < this.pointX[i])
				this.maxX = this.pointX[i];
			
			if(this.maxY < this.pointY[i])
				this.maxY = this.pointY[i];
		}
	}
	
	
	@Override
	public int getHandles(int x, int y) 
	{
		int s = HANDLE_SIZE;
		int largeur = this.maxX - this.minX;
		int hauteur = this.maxY - this.minY;
		
		if ((x >= this.minX) && (y >= this.minY) && (x <= this.minX + s) && (y <= this.minY + s)) 
			return GAUCHE_HAUT_POIGNEE;
		else if ((x >= this.minX + largeur - s) && (y >= this.minY) && (x <= this.minX + largeur) && (y <= this.minY + s))
			return DROITE_HAUT_POIGNEE;
		else if ((x >= this.minX) && (y >= this.minY + hauteur - s) && (x <= this.minX + s) && (y <= this.minY + hauteur)) 
			return GAUCHE_BAS_POIGNEE;
		else if ((x >= this.minX + largeur - s) && (y >= this.minY + hauteur - s) && (x <= this.minX + largeur) && (y <= this.minY + hauteur)) 
			return DROITE_BAS_POIGNEE;

		else if ((x >= this.minX) && (y >= this.minY + (hauteur - s) / 2) && (x <= this.minX + s) && (y <= this.minY + s + (hauteur - s) / 2)) 
			return GAUCHE_POIGNEE;
		else if ((x >= this.minX + (largeur - s) / 2) && (y >= this.minY) && (x <= this.minX + s + (largeur - s) / 2) && (y <= this.minY + s)) 
			return HAUT_POIGNEE;
		else if ((x >= this.minX + largeur - s) && (y >= this.minY + (hauteur - s) / 2) && (x <= this.minX + largeur) && (y <= this.minY + s + (hauteur - s) / 2)) 
			return DROITE_POIGNEE;
		else if ((x >= this.minX + (largeur - s) / 2) && (y >= this.minY + hauteur - s) && (x <= this.minX + s + (largeur - s) / 2) && (y <= this.minY + hauteur)) 
			return BAS_POIGNEE;
		else 
			return AUCUNE_POIGNEE;
	}
	
	
	@Override
	public Motif nouvelleInstance()
	{
		Polygone polygone = new Polygone();
		polygone.setCouleur(couleur);
		polygone.setCouleurBordure(this.couleurBordure);
		
		return polygone;
	}
	
	
	@Override
	public Motif clone()
	{
		return new Polygone(this);
	}
	
	
//======================================================================//
//============================= Méthodes ===============================//
//======================================================================//
	
	@Override
	public boolean equals(Object o)
	{
		if(this.getClass() == o.getClass())
		{
			Polygone polygone = (Polygone) o;
			boolean egale = true;
			
			egale &= this.tabPointEgale("x", polygone.pointX);
			egale &= this.tabPointEgale("y", polygone.pointY);
			
			return super.equals(polygone) && egale;
		}
		else
			return false;
	}
	
	
	/**
	 * Cette méthode permet de savoir si deux tableaux de points sont égaux.
	 * 
	 * @param typePoint Type de tableau que l'on veut tester.
	 * @param p Tableau de point à tester.
	 * @return Vrai si les deux tableaux sont égaux, sinon faux.
	 */
	public boolean tabPointEgale(String typePoint, int[] p)
	{
		int point[];
		int i = 0;
		boolean egale = true;
		
		if(typePoint == "x")
			point = this.pointX;
		else
			point = this.pointY;
		
		if(point.length == p.length)
		{
			while(i < point.length && egale)
			{
				if(point[i] != p[i])
					egale = false;
				
				i++;
			}
		}
		else
			egale = false;
		
		return egale;
	}
	
	
	/**
	 * Cette méthode calcule les points du polygone selon plusieurs paramètres (rayon, décalage, nombre de point).
	 */
	public void calculerPolygone()
	{
		this.pointX = new int[this.nbPoint];
		this.pointY = new int[this.nbPoint];
		
		this.pointX[0] = (int) (this.rayon * Math.cos (((0 * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.x;
		this.pointY[0] = (int) (this.rayon * Math.sin (((0 * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.y;
		
		this.minX = this.pointX[0];
		this.minY = this.pointY[0];
		this.maxX = this.pointX[0];
		this.maxY = this.pointY[0];
		
		for(int i = 1; i < this.nbPoint; i++)
		{
			this.pointX[i] = (int) (this.rayon * Math.cos (((i * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.x;
			this.pointY[i] = (int) (this.rayon * Math.sin (((i * Math.PI) + this.decalage) / (this.nbPoint / 2))) + this.y;
			
			if(this.minX > this.pointX[i])
				this.minX = this.pointX[i];
			
			if(this.minY > this.pointY[i])
				this.minY = this.pointY[i];
			
			if(this.maxX < this.pointX[i])
				this.maxX = this.pointX[i];
			
			if(this.maxY < this.pointY[i])
				this.maxY = this.pointY[i];
		}
	}
	

	@Override
	public String toString()
	{
		String texte = super.toString();
		
		texte += "rayon: "+this.rayon+"\n";
		texte += "decalage: "+this.decalage+"\n";
		texte += "nbPoint: "+this.nbPoint+"\n";
		texte += "minX: "+this.minX+"\n";
		texte += "minY: "+this.minY+"\n";
		texte += "maxX: "+this.maxX+"\n";
		texte += "maxY: "+this.maxY+"\n";
		
		return texte;
	}

	
//======================================================================//
//========================= Getters et Setters =========================//
//======================================================================//
	
	/**
	 * Retourne le nombre de point du polygone.
	 * 
	 * @return Le nombre de point du polygone.
	 */
	public int getNbPoint()
	{
		return nbPoint;
	}

	
	/**
	 * Cette méthode permet de modifier le nombre de point du polygone.
	 * 
	 * @param nbPoint Nouvelle valeur du nombre de point du polygone.
	 */
	public void setNbPoint(int nbPoint)
	{
		this.nbPoint = nbPoint;
	}


	/**
	 * Retourne les points de coordonnées x du polygone.
	 * 
	 * @return Les points de coordonnées x du polygone.
	 */
	public int[] getPointX()
	{
		return pointX;
	}


	/**
	 * Cette méthode permet de modifier les points de coordonnées x du polygone.
	 * 
	 * @param pointX Nouvelle valeur des points de coordonnées du polygone.
	 */
	public void setPointX(int[] pointX)
	{
		this.pointX = pointX;
	}


	/**
	 * Retourne les points de coordonnées y du polygone.
	 * 
	 * @return Les points de coordonnées y du polygone.
	 */
	public int[] getPointY()
	{
		return pointY;
	}


	/**
	 * Cette méthode permet de modifier les points de coordonnées y du polygone.
	 * 
	 * @param pointY Nouvelle valeur des points de coordonnées du polygone.
	 */
	public void setPointY(int[] pointY)
	{
		this.pointY = pointY;
	}


	/**
	 * Retourne le rayon du polygone.
	 * 
	 * @return Le rayon du polygone.
	 */
	public int getRayon()
	{
		return rayon;
	}


	/**
	 * Cette méthode permet de modifier le rayon du polygone.
	 * 
	 * @param rayon Nouvelle valeur du rayon du polygone.
	 */
	public void setRayon(int rayon)
	{
		this.rayon = rayon;
	}


	/**
	 * Retourne le décalage du polygone.
	 * 
	 * @return Le décalage du polygone.
	 */
	public int getDecalage()
	{
		return decalage;
	}


	/**
	 * Cette méthode permet de modifier le décalage du polygone.
	 * 
	 * @param decalage Nouvelle valeur du décalage du polygone.
	 */
	public void setDecalage(int decalage)
	{
		this.decalage = decalage;
	}


	/**
	 * Retourne la valeur minimal des points de coordonnées x du polygone.
	 * 
	 * @return La valeur minimal des points de coordonnées x du polygone.
	 */
	public int getMinX()
	{
		return minX;
	}


	/**
	 * Cette méthode permet de modifier la valeur minimal des points de coordonnées x du polygone.
	 * 
	 * @param minX Nouvelle valeur minimal des points de coordonnées x du polygone.
	 */
	public void setMinX(int minX)
	{
		this.minX = minX;
	}


	/**
	 * Retourne la valeur minimal des points de coordonnées y du polygone.
	 * 
	 * @return La valeur minimal des points de coordonnées y du polygone.
	 */
	public int getMinY()
	{
		return minY;
	}


	/**
	 * Cette méthode permet de modifier la valeur minimal des points de coordonnées y du polygone.
	 * 
	 * @param minY La valeur minimal des points de coordonnées y du polygone.
	 */
	public void setMinY(int minY)
	{
		this.minY = minY;
	}


	/**
	 * Retourne la valeur maximal des points de coordonnées x du polygone.
	 * 
	 * @return La valeur maximal des points de coordonnées x du polygone.
	 */
	public int getMaxX()
	{
		return maxX;
	}


	/**
	 * Cette méthode permet de modifier la valeur maximal des points de coordonnées x du polygone.
	 * 
	 * @param maxX Nouvelle valeur maximal des points de coordonnées x du polygone.
	 */
	public void setMaxX(int maxX)
	{
		this.maxX = maxX;
	}


	/**
	 * Retourne la valeur maximal des points de coordonnées y du polygone.
	 * 
	 * @return La valeur maximal des points de coordonnées y du polygone.
	 */
	public int getMaxY()
	{
		return maxY;
	}


	/**
	 * Cette méthode permet de modifier la valeur maximal des points de coordonnées y du polygone.
	 * 
	 * @param maxY Nouvelle valeur maximal des points de coordonnées y du polygone.
	 */
	public void setMaxY(int maxY)
	{
		this.maxY = maxY;
	}
}